/*
 * Copyright (c) 2009, Michael van der Westhuizen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#ifndef IMQ_UTIL_MCVDW_20090517_C
#define IMQ_UTIL_MCVDW_20090517_C

#include "imq.h"

#include "mqstatus.c"



static int setExceptionFromStatus(MQStatus status)
{
    PyObject * obj;
    MQString str;

    if (MQ_FALSE == MQStatusIsError(status)) {
        return 0;
    }

    if (NULL != (obj = mqcrt_MQStatus_Factory(status))) {
        PyObject_SetAttrString(MQStatusError, "status", obj); /* XXX: check that this increments the reference */
        Py_CLEAR(obj);
    }

    if (NULL != (str = MQGetErrorTrace())) {
        if (NULL != (obj = PyString_FromString(str))) {
            PyObject_SetAttrString(MQStatusError, "trace", obj); /* XXX: check that this increments the reference */
            Py_CLEAR(obj);
        } else {
            PyObject_SetAttrString(MQStatusError, "trace", Py_None); /* XXX: check that this increments the reference */
        }

        MQFreeString(str);
    } else {
        PyObject_SetAttrString(MQStatusError, "trace", Py_None); /* XXX: check that this increments the reference */
    }

    if (NULL != (str = MQGetStatusString(status))) {
        PyErr_SetString(MQStatusError, str);
        MQFreeString(str);
    } else {
        PyErr_SetNone(MQStatusError);
    }

    return 1;
}


static PyObject * coerceObjectToUtf8String(PyObject * obj)
{
    PyObject * obj_;
    PyObject * s;

    if (PyUnicode_Check(obj)) {
        return PyUnicode_AsUTF8String(obj);
    }

    if (NULL == (obj_ = PyUnicode_FromObject(obj))) {
        return NULL;
    }

    s = PyUnicode_AsUTF8String(obj_);
    Py_CLEAR(obj_);
    return s;
}


static int dictToProperties(MQPropertiesHandle * handle, PyObject * dict)
{
    MQStatus status;
    PyObject * key;
    PyObject * tup;
    PyObject * type;
    long type_;
    PyObject * value;
    Py_ssize_t pos;

    if (HANDLE_IS_VALID(*handle)) {
        if (setExceptionFromStatus(MQFreeProperties(*handle))) {
            return -1;
        }

        INIT_HANDLE(*handle);
    }

    if (setExceptionFromStatus(MQCreateProperties(handle))) {
        return -1;
    }

    if (NULL == dict || Py_None == dict) {
        return 0;
    }

    if (!PyDict_Check(dict)) {
        PyErr_SetString(PyExc_ValueError, "properties dictionary must be a dictionary");
        return -1;
    }
 
    pos = 0;

    while (PyDict_Next(dict, &pos, &key, &tup)) {
        if (NULL == (key = coerceObjectToUtf8String(key))) {
            return -1;
        }

        if (!PyTuple_Check(tup)) {
            Py_CLEAR(key);
            PyErr_SetString(PyExc_ValueError, "values in the properties dictionary must be tuples");
            return -1;
        }

        if (2 != PyTuple_Size(tup)) {
            Py_CLEAR(key);
            PyErr_SetString(PyExc_ValueError, "values in the properties dictionary must be tuples with a length of two");
            return -1;
        }

        if (NULL == (type = PyTuple_GetItem(tup, 0)) || NULL == (value = PyTuple_GetItem(tup, 1))) {
            Py_CLEAR(key);
            return -1;
        }

        if (-1 == (type_ = PyInt_AsLong(type))) {
            if (PyErr_Occurred()) {
                Py_CLEAR(key);
                return -1;
            }
        }

        switch (type_) {
            case MQ_BOOL_TYPE: {
                int res;

                if (-1 == (res = PyObject_IsTrue(value))) {
                    Py_CLEAR(key);
                    return -1;
                }

                status = MQSetBoolProperty(*handle, PyString_AsString(key), res == 0 ? MQ_FALSE : MQ_TRUE);
                break;
            }
            case MQ_INT8_TYPE: {
                long i;

                PyErr_Clear();

                if (-1 == (i = PyInt_AsLong(value))) {
                    if (PyErr_Occurred()) {
                        Py_CLEAR(key);
                        return -1;
                    }
                }

                if (i > SCHAR_MAX || i < SCHAR_MIN) {
                    Py_CLEAR(key);
                    PyErr_SetString(PyExc_ValueError, "value must be an 8 bit signed integer");
                    return -1;
                }

                status = MQSetInt8Property(*handle, PyString_AsString(key), (MQInt8)i);
                break;
            }
            case MQ_INT16_TYPE: {
                long i;

                PyErr_Clear();

                if (-1 == (i = PyInt_AsLong(value))) {
                    if (PyErr_Occurred()) {
                        Py_CLEAR(key);
                        return -1;
                    }
                }

                if (i > SHRT_MAX || i < SHRT_MIN) {
                    Py_CLEAR(key);
                    PyErr_SetString(PyExc_ValueError, "value must be a 16 bit signed integer");
                    return -1;
                }

                status = MQSetInt16Property(*handle, PyString_AsString(key), (MQInt16)i);
                break;
            }
            case MQ_INT32_TYPE: {
                long i;

                PyErr_Clear();

                if (-1 == (i = PyInt_AsLong(value))) {
                    if (PyErr_Occurred()) {
                        Py_CLEAR(key);
                        return -1;
                    }
                }

                if (i > INT_MAX || i < INT_MIN) {
                    Py_CLEAR(key);
                    PyErr_SetString(PyExc_ValueError, "value must be a 32 bit signed integer");
                    return -1;
                }

                status = MQSetInt32Property(*handle, PyString_AsString(key), (MQInt32)i);
                break;
            }
            case MQ_INT64_TYPE: {
                MQInt64 i;

                PyErr_Clear();

                i = PyLong_AsLongLong(value);

                if (PyErr_Occurred()) {
                    Py_CLEAR(key);
                    return -1;
                }

                status = MQSetInt64Property(*handle, PyString_AsString(key), i);
                break;
            }
            case MQ_FLOAT32_TYPE: {
                MQFloat32 i;

                PyErr_Clear();

                i = (MQFloat32)PyFloat_AsDouble(value);

                if (PyErr_Occurred()) {
                    Py_CLEAR(key);
                    return -1;
                }

                status = MQSetFloat32Property(*handle, PyString_AsString(key), i);
                break;
            }
            case MQ_FLOAT64_TYPE: {
                MQFloat64 i;

                PyErr_Clear();

                i = (MQFloat64)PyFloat_AsDouble(value);

                if (PyErr_Occurred()) {
                    Py_CLEAR(key);
                    return -1;
                }

                status = MQSetFloat64Property(*handle, PyString_AsString(key), i);
                break;
            }
            case MQ_STRING_TYPE: {
                if (NULL == (value = coerceObjectToUtf8String(value))) {
                    Py_CLEAR(key);
                    return -1;
                }

                status = MQSetStringProperty(*handle, PyString_AsString(key), PyString_AsString(value));
                Py_CLEAR(value);
                break;
            }
            default: {
                Py_CLEAR(key);
                PyErr_SetString(PyExc_ValueError, "first tuple element must be a valid IMQ datatype");
                return -1;
            }
        }

        Py_CLEAR(key);

        if (setExceptionFromStatus(status)) {
            return -1;
        }
    }

    return 0;
}


static int propertiesToDict(PyObject * dict, MQPropertiesHandle handle)
{
    ConstMQString key_;
    MQType type;
    PyObject * key;
    PyObject * value;
    PyObject * item;

    PyDict_Clear(dict);

    if (setExceptionFromStatus(MQPropertiesKeyIterationStart(handle))) {
        return -1;
    }

    while (MQPropertiesKeyIterationHasNext(handle)) {
        if (setExceptionFromStatus(MQPropertiesKeyIterationGetNext(handle, &key_))) {
            return -1;
        }

        if (setExceptionFromStatus(MQGetPropertyType(handle, key_, &type))) {
            return -1;
        }

        switch (type) {
            case MQ_BOOL_TYPE: {
                MQBool v;

                if (setExceptionFromStatus(MQGetBoolProperty(handle, key_, &v))) {
                    return -1;
                }

                value = PyBool_FromLong(MQ_TRUE == v ? 1L : 0L);
                break;
            }
            case MQ_INT8_TYPE: {
                MQInt8 v;

                if (setExceptionFromStatus(MQGetInt8Property(handle, key_, &v))) {
                    return -1;
                }

                value = Py_BuildValue("b", v);
                break;
            }
            case MQ_INT16_TYPE: {
                MQInt16 v;

                if (setExceptionFromStatus(MQGetInt16Property(handle, key_, &v))) {
                    return -1;
                }

                value = Py_BuildValue("h", v);
                break;
            }
            case MQ_INT32_TYPE: {
                MQInt32 v;

                if (setExceptionFromStatus(MQGetInt32Property(handle, key_, &v))) {
                    return -1;
                }

                value = Py_BuildValue("i", v);
                break;
            }
            case MQ_INT64_TYPE: {
                MQInt64 v;

                if (setExceptionFromStatus(MQGetInt64Property(handle, key_, &v))) {
                    return -1;
                }

                value = Py_BuildValue("L", v);
                break;
            }
            case MQ_FLOAT32_TYPE: {
                MQFloat32 v;

                if (setExceptionFromStatus(MQGetFloat32Property(handle, key_, &v))) {
                    return -1;
                }

                value = Py_BuildValue("f", v);
                break;
            }
            case MQ_FLOAT64_TYPE: {
                MQFloat64 v;

                if (setExceptionFromStatus(MQGetFloat64Property(handle, key_, &v))) {
                    return -1;
                }

                value = Py_BuildValue("d", v);
                break;
            }
            case MQ_STRING_TYPE: {
                ConstMQString v;
                PyObject * obj;

                if (setExceptionFromStatus(MQGetStringProperty(handle, key_, &v))) {
                    return -1;
                }

                if (NULL == (obj = PyUnicode_DecodeUTF8(v, strlen(v), "strict"))) {
                    return -1;
                }

                value = Py_BuildValue("O", obj);
                Py_CLEAR(obj);
                break;
            }
            default: {
                PyErr_Format(PyExc_ValueError, "%d is not a supported datatype", type);
                return -1;
            }
        }

        if (NULL == value) {
            return -1;
        }

        if (NULL == (item = Py_BuildValue("(iO)", type, value))) {
            Py_CLEAR(value);
            return -1;
        }

        Py_CLEAR(value);

        if (NULL == (key = PyUnicode_DecodeUTF8(key_, strlen(key_), "strict"))) {
            Py_CLEAR(item);
            return -1;
        }

        if (0 != PyDict_SetItem(dict, key, item)) {
            Py_CLEAR(item);
            Py_CLEAR(key);
            return -1;
        }

        Py_CLEAR(item);
        Py_CLEAR(key);
    }

    return 0;
}

#endif
